#include "mode.h"
#include "main.h"

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "fatfs.h"
//#include "libjpeg.h"
#include "usb_device.h"

//#include "../lv_lib_png/lodepng.h"
#include <limits.h>

#define MAX_BMP_FILES 32//32
#define MAX_IMAGE_FILE_LEN 32//32
#define DEFAULT_IMAGE_CHANGE_INTERVAL 1000
//#define blockSize 8
#define linecount 30//20
#define GRamSize (linecount*LCD_Width*3)
__attribute__((section(".ccmram"))) uint8_t lineData[GRamSize] = {0};

FRESULT res;
struct RA8876_Pixel_Mode2 {
	uint8_t b;
	uint8_t g;
	uint8_t r;
} __attribute__((packed));  


typedef struct __attribute__((packed)) tagBITMAPFILEHEADER {
	uint16_t bFileType;
	uint32_t bFileSize;
	uint32_t bReserved1;
	//uint16_t bReserved2;
	uint32_t bPixelDataOffset;
} BITMAPFILEHEADER; //14bytes 
 /* __attribute__((packed))，它的作用就是告诉编译器：
 取消结构体在编译过程中的优化对齐，按尽可能小的size对齐——也就是按1字节为单位对齐。*/

//typedef struct tagBITMAPFILEHEADER BITMAPFILEHEADER;

typedef struct __attribute__((packed)) tagBITMAPINFOHEADER {
	uint32_t bHeaderSize;  // 图像信息头总大小（40bytes）
	uint32_t bImageWidth;  // 图像宽度（像素）
	uint32_t bImageHeight;  // 图像高度
	uint16_t bPlanes;  // 应该是0
	uint16_t bBitsPerPixel;  // 像素位数
	uint32_t bCompression;  // 图像压缩方法
	uint32_t bImageSize;  // 图像大小（字节）
	uint32_t bXpixelsPerMeter;  // 横向每米像素数
	uint32_t bYpixelsPerMeter;  // 纵向每米像素数
	uint32_t bTotalColors;  // 使用的颜色总数，如果像素位数大于8，则该字段没有意义
	uint32_t bImportantColors;  // 重要颜色数，一般没什么用
}BITMAPINFOHEADER; //40byte

//typedef struct tagBITMAPINFOHEADER BITMAPINFOHEADER;


extern SD_HandleTypeDef hsd;
extern UART_HandleTypeDef huart1;
//extern SDRAM_HandleTypeDef hsram1;

const char Title[] = {" \r\n\
 _____            _     _   __\r\n\
(_   _)          ( )_ /  )  _  \\\r\n\
  | |    __   ___|  _)_  | ( ) |\r\n\
  | |  / __ \\  __) |   | | | | |\r\n\
  | | (  ___/__  \\ |_  | | (_) |\r\n\
  (_)  \\____)____/\\__) (_)\\___/\r\n\
   \r\n\
Copyright (c) zhaitao.as@outlook.com\r\n\
|||||||||||||||||||||||||||||||||||||\r\n\
\r\n\
"};

uint32_t testInterval = DEFAULT_IMAGE_CHANGE_INTERVAL;
FATFS fs;
FIL fil;
FRESULT fr;
uint32_t br;
uint8_t imageFileCount = 0;
char imageFileNames[MAX_BMP_FILES][MAX_IMAGE_FILE_LEN];
char *imageFileSorted[MAX_BMP_FILES];
//static uint8_t lineData[2048 * 3 * blockSize];


uint8_t rd;
uint8_t getUartFlag = 0;

/////////////////
uint32_t file_byte = 0;		  //文件大小（字节数）
uint32_t byteswritten = 0;    // 写文件计数
uint32_t bytesread = 0;       // 读文件计数

uint8_t wtext[] = "THAT IS ME 4G CARD"; // 写的内容
uint8_t rtext[1024];             	// 读取的buff,1024bytes

char filename[] = "log.txt"; // 文件名
extern uint8_t retSD; /* Return value for SD */
///////////////////

int mipiCheckIDDcs(void);
int mipiCheckIDGen(void);

/**
 * @brief get image (bmp, jpg) file name from sdcard
 * 
 * @param path folder path
 * @param ifn image file names buffer
 * @return int file number
 */
int getSDCardImageFileNames(char *path, char *ifn[])
{
	FILINFO info;
	DIR file_dir;
	FRESULT fr;
	int cnt = 0;
	
	fr = f_opendir(&file_dir, path);
	if (fr) {		
		printf("FATFS f_opendir ERROR\r\n");
		Error_Handler();
	}
	
	while (1) {
		fr = f_readdir(&file_dir, &info);

		if (fr || info.fname[0] == 0)
			break;
		if (info.fname[0] == '.')
			continue;
		if (strstr(info.fname, ".bmp") || strstr(info.fname, ".BMP")) {
			
			// only get file name in number
			char fileName[32];
			strcpy(fileName, info.fname);
			//printf("fileName ==== %s \r\n",fileName);
			char *pch;
			pch = strtok(fileName, ".");
			if (atoi(pch)) {
				strcpy(ifn[cnt], info.fname);
				cnt++;
			}
		}
	}

	return cnt;
}

void SDCardCheck(void)
{
	int result = 0;

	//
	res = f_mount(&fs, "", 0);
	if (res) {
		printf("SD Check f_mount error %d\r\n", res);
		result += 1;
	}

	//
	res = f_open(&fil, "FatFs.txt", FA_OPEN_ALWAYS | FA_WRITE);
	if (res) {
		printf("SD Check f_open error %d\r\n", res);
		result += 1;
	}

	//
	uint8_t wtext[] = "SD Check: Read from write file SUCCESS\r\n";
	uint8_t rtext[100];
	uint32_t byteswritten;
	uint32_t bytesread;
	res = f_write(&fil, wtext, sizeof(wtext), (void *)&byteswritten);
	if (res) {
		printf("SD Check f_write error %d\r\n", res);
		result += 1;
	}

	//
	res = f_close(&fil);
	if (res) {
		printf("SD Check f_close error %d\r\n", res);
		result += 1;
	}

	//
	res = f_open(&fil, "FatFs.txt", FA_READ | FA_OPEN_EXISTING);
	if (res != FR_OK) {
		printf("SD Check f_open error %d\r\n", res);
		result += 1;
	}

	//
	res = f_read(&fil, rtext, 54, (UINT *)&bytesread);
	if (res != FR_OK) {
		printf("SD Check read error %d\r\n", res);
		result += 1;
	}

	//
	res = f_close(&fil);
	if (res) {
		printf("SD Check f_close error %d\r\n", res);
		result += 1;
	}

	// remove testing file
	res = f_unlink("Fatfs.txt");
	if (res) {
		printf("SD Check f_unlink error %d\r\n", res);
		result += 1;
	}

	if (result != 0) {
		printf("SDcard check failed\r\n");
		printf("SD Check write %d bytes, read %d bytes\r\n",
		       (int)byteswritten, (int)bytesread);
		printf((const char *)rtext);

		Error_Handler();
	} else {
		printf("[SDcard] Check Success\r\n");
	}

	return;
}

uint32_t getBmpRawOffset(uint8_t *BMP_Header)
{
	/*uint32_t fileSize = 0;
	fileSize = ((uint32_t)(BMP_Header[5]) << 24) +
		   ((uint32_t)(BMP_Header[4]) << 16) +
		   ((uint32_t)(BMP_Header[3]) << 8) +
		   ((uint32_t)(BMP_Header[2]) << 0);
	// printf("filesize: %d\r\n", fileSize);

	uint32_t rawDataSize = 0;
	rawDataSize = ((uint32_t)(BMP_Header[37]) << 24) +
		      ((uint32_t)(BMP_Header[36]) << 16) +
		      ((uint32_t)(BMP_Header[35]) << 8) +
		      ((uint32_t)(BMP_Header[34]) << 0);
	// printf("rawDataSize: %d\r\n", rawDataSize);

	return fileSize - rawDataSize;*/
	return BMP_Header[2];
}

uint8_t bmpDrawFile(char *fileName)
{

	int __error = 0;
	// static FIL fil;
	uint8_t BMP_Header[54];
	uint16_t width, height;
	volatile uint16_t x;
	volatile struct RA8876_Pixel_Mode2 *pixel;

	printf("bmpDrawFile: %s \r\n", fileName);

	res = f_open(&fil, (const TCHAR *)fileName, FA_READ | FA_OPEN_EXISTING);
	if (res != FR_OK) {
		printf("bmpDrawFile f_open error %d\r\n", res);
		__error = 2;
		goto bmpDrawFile_close;
	}

	//
	res = f_read(&fil, BMP_Header, 54, (UINT *)&br);
	if (res != FR_OK) {
		printf("bmpDrawFile file read error\r\n");
		__error = 3;
		goto bmpDrawFile_close;
	}

	BITMAPFILEHEADER *fh = (BITMAPFILEHEADER *)BMP_Header;//前14字节
	BITMAPINFOHEADER *ih = (BITMAPINFOHEADER *)(BMP_Header + 14);//54-14=40字节

	if (fh->bFileType != 0x4D42) {
		printf("bmpDrawFile file type should 0x4D42, get: %04x\r\n", fh->bFileType);
		__error = 1;
		goto bmpDrawFile_close;
	}

	width = ih->bImageWidth;
	height = ih->bImageHeight;
	uint32_t lineDataSize = width * 3; /* width * 3, In Byte */

	if (lineDataSize%4) {//BMP格式，170*3除4，有余数，自动补了两个字节；
		lineDataSize += 4 - lineDataSize%4; 
	}
	uint32_t filesize = fh->bPixelDataOffset + lineDataSize * height;
	uint32_t lineCountGRamCache = GRamSize / lineDataSize;

	static uint32_t lineStartPosition = 0;
	static uint32_t br = 0;
	uint32_t lineDrawed = 0;
	uint32_t lineToCache = 0;
	uint32_t dataDrawed = 0;
	uint32_t dataToDraw = 0;
	//wr_c2c();
	wr_cmd_4spi_8bcs(0x2A); 
	wr_num(0x00);
	wr_num(0x0);
	wr_num(0x00);
	wr_num(0xef);//239

	wr_cmd_4spi_8bcs(0x2B); 
	wr_num(0x00);
	wr_num(0x00);
	wr_num(0x01);
	wr_num(0x3f);//319

	wr_c2c(0x2c);

	wr_cmd_4spi_8bcs(0x2A);
    wr_dat_4spi_8bcs(0x00);
    wr_dat_4spi_8bcs(0x00);
    wr_dat_4spi_8bcs(0x00);
    wr_dat_4spi_8bcs(0xB3);
    wr_cmd_4spi_8bcs(0x2B);
    wr_dat_4spi_8bcs(0x00);
    wr_dat_4spi_8bcs(0x00);
    wr_dat_4spi_8bcs(0x01);
    wr_dat_4spi_8bcs(0xC5);
    wr_cmd_4spi_8bcs(0x2C);

	while (lineDrawed < height) {

		/* Calculate cache size */
		if ((lineDrawed + lineCountGRamCache) < height) {
			// 没到图片结尾, 缓存gram所能容纳的最大行数
			lineToCache = lineCountGRamCache;
		} else {
			// 到了结尾, 只需要缓存一部分行
			lineToCache = height - lineDrawed;
		}

		/* 	Calculate new cache area start position
			bmp图片是y翻转存放的, 文件头之后是图片最后一行, 文件结尾是图片的第一行
			为了保准观感上刷图是从屏的0行开始, 要先从文件结尾部分开始刷
			先找到文件结尾, 再减去已经刷过的行, 再减去这一次要刷的行, 就是新的起点
		*/
		dataDrawed = lineDrawed * lineDataSize;
		dataToDraw = lineToCache * lineDataSize;
		br = filesize - dataDrawed - dataToDraw;

		/* Move Pointer to position of new cache */
		if (f_lseek(&fil, br)) {
			printf("bmpDrawFile f_lseek error\r\n");
			__error = 3;
			goto bmpDrawFile_close;
		}
		/* Read Line Data */
		if (f_read(&fil, lineData, dataToDraw, (UINT *)&br)) {
			printf("bmpDrawFile f_read error\r\n");
			__error = 4;
			goto bmpDrawFile_close;
		}
		/* Draw cached data */
		uint8_t color1,color2;
		for (uint32_t i = 0; i < lineToCache; i++) {
			lineStartPosition = (lineToCache - 1 - i) * lineDataSize;
			pixel = (struct RA8876_Pixel_Mode2 *)(lineData + lineStartPosition);

			/* Draw one line of cached*/
			for (x = 0; x < width; x++) {
				color1 = (pixel->r & 0xf8) | ((pixel->g & 0xfc)>>5);//16bit
				color2 = ((pixel->g & 0xfc)<<3) | ((pixel->b & 0xf8)>>3);
				//printf("pixel->r = %ld,pixel->g = %ld,pixel->b = %ld\n\r",pixel->r,pixel->g,pixel->b);
				wr_num(color1);
				wr_num(color2);
				//wr_num(pixel->r);
				//wr_num(pixel->g);
				//wr_num(pixel->b);	
				pixel++;
			}
		}
		lineDrawed += lineToCache;
	}

bmpDrawFile_close:

	if (f_close(&fil)) {
		printf("bmpDrawFile f_close error\r\n");
	}

	return __error;
}
uint8_t drawFile(char *fn)
{
	uint8_t r = 0;

	HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_RESET);

		if (strstr(fn, ".bmp") || strstr(fn, ".BMP")) {
			bmpDrawFile(fn);
		} else if (strstr(fn, ".jpg")) {
			//jpgDrawFile(fn);
		} else {
			r = -1;
		}

	HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_SET);

	return r;
}

/**
 * @retval None
 */

/************************************************************************************************
 * 
 *  								Functions Export to Lua
 * 
 ***********************************************************************************************/
 /* @brief sort string
 * 
 * @param strings 
 * @param num 
 */
void sortStrings(char* strings[], int num)
{
    char *temp;
    int top, seek;

    for (top = 0; top < num-1; top++)
    {
        for (seek = top + 1; seek < num; seek++)
        {
            if (strcmp(strings[top],strings[seek]) > 0)
            {
                temp = strings[top];
                strings[top] = strings[seek];
                strings[seek] = temp;
            }
        }
    }
}

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	UNUSED(huart);
	//HAL_UART_Transmit(&huart1, &rd, 1, 0xfff);
	getUartFlag = 1;
	//HAL_UART_Receive_IT(&huart1, &rd, 1);
}

int testModeStart(void)
{
	int result = 0;
	SDCardCheck(); ////Check sdcard 
	
	// Get sdcard file names and sort
	do {
		for (int c = 0; c < MAX_BMP_FILES; c++) {
			imageFileSorted[c] = imageFileNames[c];
		}
		imageFileCount = getSDCardImageFileNames("", imageFileSorted);

		sortStrings(imageFileSorted, imageFileCount);

		printf("[LCMTEST] Sorted Image Files:\r\n");
		for (uint8_t i = 0; i < imageFileCount; i++) {
			printf("\t%s ", imageFileSorted[i]);
			if ((i > 0) && (i % 8 == 0)) {
				printf("\r\n");
			}
		}
	} while (0);


	// Main Loop With Key Pause 
	uint8_t pauseFlag = 0;
	uint32_t timePiece = testInterval + 1;
	uint8_t imageIndex = 0;
	////HAL_UART_Receive_IT(&huart1, &rd, 1);
	while (1) {
		// Detect Key State, and Change pauseFlag 
		if (HAL_GPIO_ReadPin(SW1_GPIO_Port, SW1_Pin) == GPIO_PIN_RESET) {
			HAL_Delay(20);
			while (HAL_GPIO_ReadPin(SW1_GPIO_Port, SW1_Pin) == GPIO_PIN_RESET) {
			};
			//printf("HAS COUNT \r\n");
			if (pauseFlag == 0) {
				pauseFlag = 1;
				timePiece = 0;
				HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_RESET);
			} else {
				pauseFlag = 0;
				HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_SET);
				timePiece = testInterval + 1; // change image immediately 
			}
		}
		// Detect pause flag, if flag, timePiece++, else keep remain 
		if (pauseFlag == 0) {
			timePiece++;
		}

		// Detect timePiece, if timePiece >= 1000, change display image,and make timePiece 0//
		if (timePiece >= testInterval) {
			timePiece = 0;

			if (imageIndex >= imageFileCount) {
				imageIndex = 0;
			}
			//printf("imageIndex  == %d ,testInterval  == %d \n\r ",imageIndex,testInterval);
			drawFile(imageFileSorted[imageIndex]);
			imageIndex++;
		}

		// time piece interval = 1ms 
		HAL_Delay(1);
	}
	return result;
}
